package core;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import util.Constants;

public class IdxFile {
    private String strFileName; // path to the ".idx" file.
    private long longWordCount; // number of entries.
    private long longIdxFileSize;
    
    public boolean boolIsLoaded = false; // decide if the properties are loaded
    public List<WordEntry> entryList; // store the list of entries
    public String getStrFileName() {
		return strFileName;
	}

	public void setStrFileName(String strFileName) {
		this.strFileName = strFileName;
	}

	public long getLongWordCount() {
		return longWordCount;
	}
	public long getLongIdxFileSize() {
		return longIdxFileSize;
	}

	public void setLongIdxFileSize(long longIdxFileSize) {
		this.longIdxFileSize = longIdxFileSize;
	}
    // constructor
    public IdxFile(String fileName, long wordCount, long fileSize) {
        this.longWordCount = wordCount;
        this.longIdxFileSize = fileSize;
        this.strFileName = fileName;
        load();
//        System.out.println("idx" + this.boolIsLoaded);
    }

    // load properties;
    public void load() {
        if (boolIsLoaded || ((new java.io.File(strFileName)).exists()) == false) {
            return;
        }
        try {
            DataInputStream dt = new DataInputStream(new BufferedInputStream(new FileInputStream(strFileName)));
            byte[] bt = new byte[(int) longIdxFileSize];
            dt.read(bt);
            dt.close();
            entryList = new ArrayList<WordEntry>();
            int startPos; // start position of entry
            int endPos = Constants.FIRST_POS_INDEX_FILE; // end position of entry

            /*
             * firstBytes = new byte[Constants.FIRST_POS_INDEX_FILE]; for(int i=0;i<Constants.FIRST_POS_INDEX_FILE;i++)
             * firstBytes[i] = bt[i];
             */
            WordEntry tempEntry = null;
            for (long i = 0; i < longWordCount; i++) {
                tempEntry = new WordEntry();
                // read the word
                startPos = endPos;
                while (bt[endPos] != '\0') {
                    endPos++;
                }
                tempEntry.strWord = new String(bt, startPos, endPos - startPos, "UTF8");
                tempEntry.strLwrWord = tempEntry.strWord.toLowerCase();
                // read the offset of the meaning (in .dict file)
                ++endPos;
                tempEntry.longOffset = readAnInt32(bt, endPos);
                // read the size of the meaning (in .dict file)
                endPos += 4;
                tempEntry.longSize = readAnInt32(bt, endPos);
                endPos += 4;
                entryList.add(tempEntry);
            }
            boolIsLoaded = true;
        } catch (Exception ex) {
        }
    }
    // reload  .idx file
    public void reload() {
        boolIsLoaded = false;
        load();
    }

    // convert 4 char array to an integer
    private long readAnInt32(byte[] str, int beginPos) {
        int firstByte = (0x000000FF & ((int) str[beginPos]));
        int secondByte = (0x000000FF & ((int) str[beginPos + 1]));
        int thirdByte = (0x000000FF & ((int) str[beginPos + 2]));
        int fourthByte = (0x000000FF & ((int) str[beginPos + 3]));
        return ((long) (firstByte << 24 | secondByte << 16 | thirdByte << 8 | fourthByte)) & 0xFFFFFFFFL;
    }

    private byte[] convertAnInt32(int val) {
        byte[] str = new byte[4];
        str[0] = (byte) ((val & 0xFF000000) >> 24);
        str[1] = (byte) ((val & 0x00FF0000) >> 16);
        str[2] = (byte) ((val & 0x0000FF00) >> 8);
        str[3] = (byte) ((val & 0x000000FF));
        return str;
    }

    // return the index of a word in entry list
    public long findIndexForWord(String word) {
        if (!boolIsLoaded) {
            return longWordCount;
        }
        long first = 0;
        long last = (int) longWordCount - 1;
        long mid;
        String lwrWord = word.toLowerCase();
        // use binary search
        do {
            mid = (first + last) / 2;
            int cmp = lwrWord.compareTo(((WordEntry) entryList.get((int) mid)).strLwrWord);
            if (cmp == 0) {
                return mid; // return index if found
            }
            if (cmp > 0) {
                first = mid + 1;
            } else {
                last = mid - 1;
            }
        } while (first <= last);
        // if not found
        if (first < longWordCount) {
            while (first < longWordCount) {
                if (((WordEntry) entryList.get((int) first)).strLwrWord.compareTo(lwrWord) > 0) {
                    break;
                } else {
                    first++;
                }
            }
        }
        return first;
    }

    public boolean write(String fileName) {
        try {
            DataOutputStream dt = new DataOutputStream(new FileOutputStream(fileName));
            // dt.write(firstBytes,0,Constants.FIRST_POS_INDEX_FILE);
            WordEntry tempEntry = null;
            for (int i = 0; i < (int) longWordCount; i++) {
                tempEntry = entryList.get(i);
                dt.write(tempEntry.strWord.getBytes("UTF8"));
                dt.write('\0');
                dt.write(convertAnInt32((int) tempEntry.longOffset));
                dt.write(convertAnInt32((int) tempEntry.longSize));
            }
            dt.flush();
            dt.close();
            return true;
        } catch (Exception ex) {
        }
        return false;
    }

    public boolean write() {
        return write(strFileName);
    }

    public boolean addEntry(String word, long offset, long size, int addPos) {
        WordEntry etr = new WordEntry();
        etr.strWord = word;
        etr.strLwrWord = word.toLowerCase();
        etr.longOffset = offset;
        etr.longSize = size;
        if (addPos == -1) {
            addPos = (int) findIndexForWord(etr.strLwrWord);
        }
        if (addPos == longWordCount) {
            entryList.add(etr);
            this.longWordCount++;
            longIdxFileSize += (9 + word.length());
            return true;
        } else if (etr.strLwrWord.compareTo(((WordEntry) this.entryList.get(addPos)).strLwrWord) != 0) {
            entryList.add(addPos, etr);
            this.longWordCount++;
            longIdxFileSize += (9 + word.length());
            return true;
        }
        return false;
    }

    public boolean removeEntry(String word) {
        String strLwrWord = word.toLowerCase();
        int pos = (int) findIndexForWord(strLwrWord);
        if (pos == longWordCount) {
            return false;
        } else if (strLwrWord.compareTo(((WordEntry) entryList.get(pos)).strLwrWord) != 0) {
            return false;
        } else {
            this.longWordCount--;
            longIdxFileSize -= (9 + word.length());
            entryList.remove(pos);
            return true;
        }
    }
}
